home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Just Call Me Internet
/
Just Call Me Internet.iso
/
archives
/
com
/
internet
/
stik
/
gls002b5.zoo
/
transdmn.c
< prev
next >
Wrap
C/C++ Source or Header
|
1997-09-21
|
40KB
|
1,581 lines
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <support.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <ioctl.h>
#include <fcntl.h>
#include <arpa/nameser.h> /* for <resolv.h> */
#define __const const /* grrrr... */
#include <resolv.h> /* for h_error */
#include <mintbind.h>
#define DAEMON /* to get "int caller_pid, " */
#include "global.h"
#include "pipe.h"
/* To facilitate two-step listen/accept without changing the connection
number, as required by STiK semantics, we make the connection number
a magic cookie that gets translated to the real fd. */
static unsigned char cookie_counter = 0;
/* If -DMULTITHREAD is enabled, don't call NEXT_LISTEN_COOKIE() unless
* you're holding the FD_SEM semaphore. */
#define NEXT_LISTEN_COOKIE() (int)(('L'<<8)|(cookie_counter++))
#define IS_LISTEN_COOKIE(fd) ((((fd)>>8)&0xFF)=='L')
#define REAL_FD(pid,fd) (IS_LISTEN_COOKIE(fd)?get_real_fd(pid,fd):(fd))
/* MiNTnet's fcntl(FIONREAD) returns this when the socket shuts down, or
an error condition occurs, or there's no more to read. */
#define NO_LIMIT 0x7FFFFFFFL
typedef struct CIB_chain {
int the_fd;
int real_fd;
#ifndef BLOCK_OPEN
int established;
#endif
CIB the_CIB;
struct CIB_chain *next;
} CIB_chain;
#if 0
Daemon_Op OP;
Daemon_Retval RET;
#endif
int multithread = 0;
int daemon_pid;
CIB_chain *CIB_list = 0;
static int daemon_pipe;
int init_net()
{
daemon_pid = Pgetpid();
if (Psem_create(CIB_SEM) < 0) {
Cconws("Unable to create CIB semaphore\r\n");
return 0;
}
/* We own the semaphores when they're created; release them so we can
re-grab them later */
Psem_release(CIB_SEM);
#ifdef MULTITHREAD
if (Psem_create(FD_SEM) < 0) {
Cconws("Unable to create file-descriptor semaphore\r\n");
return 0;
}
Psem_release(FD_SEM);
{
/* Enable multithreading if requested. */
register char *s = do_getvstr(daemon_pid, "MULTITHREAD");
if (!strcmp(s, "1") || !stricmp(s, "ON") || !stricmp(s, "TRUE"))
multithread = 1;
}
#endif /* MULTITHREAD */
return 1;
}
static int dispatch(long msg_ptr)
{
PMSG pmsg;
Daemon_Op *OP;
Daemon_Retval *RET;
/* BUG: This is a potential race condition; if the parent gets
another message and writes it over the original before we get a
chance to copy the original, the original will be lost.
Unfortunately, I can't think of a solution that doesn't also assume
that the child thread will go first. */
pmsg = *(PMSG *)msg_ptr;
OP = (Daemon_Op *)pmsg.userlong1;
RET = (Daemon_Retval *)pmsg.userlong2;
#ifdef DEBUG
LOG(pmsg.pid, DBG_TRACE, "Received op %O", OP->common.op);
#if 0
debug("sOsd", "Received op ", OP->common.op, " from pid ", pmsg.pid);
#endif
#endif
switch (OP->common.op) {
case OP_TCP_OPEN:
RET->ret_int16 = do_TCP_open(pmsg.pid, OP->P_TCP_open.hostaddr,
OP->P_TCP_open.port, OP->P_TCP_open.tos,
OP->P_TCP_open.obsize);
break;
case OP_TCP_CLOSE:
RET->ret_int16 = do_TCP_close(pmsg.pid, OP->P_TCP_close.fd,
OP->P_TCP_close.timeout);
break;
case OP_TCP_SEND:
RET->ret_int16 = do_TCP_send(pmsg.pid, OP->P_TCP_send.fd,
OP->P_TCP_send.buf, OP->P_TCP_send.buflen);
break;
case OP_TCP_WAIT_STATE:
RET->ret_int16 = do_TCP_wait_state(pmsg.pid, OP->P_TCP_wait_state.fd,
OP->P_TCP_wait_state.state,
OP->P_TCP_wait_state.timeout);
break;
case OP_UDP_OPEN:
RET->ret_int16 = do_UDP_open(pmsg.pid, OP->P_UDP_open.hostaddr,
OP->P_UDP_open.port);
break;
case OP_UDP_CLOSE:
RET->ret_int16 = do_UDP_close(pmsg.pid, OP->P_UDP_close.fd);
break;
case OP_UDP_SEND:
RET->ret_int16 = do_UDP_send(pmsg.pid, OP->P_UDP_send.fd,
OP->P_UDP_send.buf, OP->P_UDP_send.buflen);
break;
case OP_CNBYTE_COUNT:
RET->ret_int16 = do_CNbyte_count(pmsg.pid, OP->P_CNbyte_count.fd);
break;
case OP_CNGET_CHAR:
RET->ret_int16 = do_CNget_char(pmsg.pid, OP->P_CNget_char.fd);
break;
case OP_CNGET_NDB:
RET->ret_NDBptr = do_CNget_NDB(pmsg.pid, OP->P_CNget_NDB.fd);
break;
case OP_CNGET_BLOCK:
RET->ret_int16 = do_CNget_block(pmsg.pid, OP->P_CNget_block.fd,
OP->P_CNget_block.buf,
OP->P_CNget_block.len);
break;
case OP_RESOLVE:
RET->ret_int16 = do_resolve(pmsg.pid, OP->P_resolve.hostname,
OP->P_resolve.realname,
OP->P_resolve.addrs,
OP->P_resolve.naddrs);
break;
case OP_CNGETINFO:
RET->ret_CIBptr = do_CNgetinfo(pmsg.pid, OP->P_CNgetinfo.fd);
break;
case OP_KRMALLOC:
RET->ret_charptr = do_KRmalloc(pmsg.pid, OP->P_KRmalloc.size);
break;
case OP_KRFREE:
do_KRfree(pmsg.pid, OP->P_KRfree.mem);
RET->ret_int32 = 0;
break;
case OP_KRGETFREE:
RET->ret_int32 = do_KRgetfree(pmsg.pid, OP->P_KRgetfree.flag);
break;
case OP_KRREALLOC:
RET->ret_charptr = do_KRrealloc(pmsg.pid, OP->P_KRrealloc.mem,
OP->P_KRrealloc.newsize);
break;
case OP_GETVSTR:
RET->ret_charptr = do_getvstr(pmsg.pid, OP->P_getvstr.var);
break;
case OP_SETVSTR:
RET->ret_int16 = do_setvstr(pmsg.pid, OP->P_setvstr.var,
OP->P_setvstr.value);
break;
default:
#ifdef DEBUG
LOG(pmsg.pid, DBG_ERROR, "in dispatch(): Unrecognized opcode %d",
OP->common.op);
#if 0
debug("sd", "in dispatch(): unrecognized opcode ", OP->common.op);
#endif
#endif
break;
}
#ifdef DEBUG
LOG(pmsg.pid, DBG_TRACE, "Sending reply to op %O", OP->common.op);
#if 0
debug("sOsd", "Sending reply to op ", OP->common.op, " from pid ", pmsg.pid);
#endif
#endif
Pmsg_send(0xFFFF0000L | pmsg.pid, &pmsg);
}
void main_loop()
{
PMSG pmsg;
int n;
for (;;) {
n = Pmsg_recv(DMN_MBOX, &pmsg);
if (n < 0) {
#ifdef DEBUG
LOG(daemon_pid, DBG_FATAL, "Pmsg() returned %d", n);
#endif
exit(1);
}
#ifdef MULTITHREAD
if (multithread) {
tfork(dispatch, (long)(&pmsg));
/* Maximize child thread's chance of copying the pmsg before we
overwrite it */
Syield();
} else
#endif
dispatch((long)(&pmsg));
}
}
static int make_CIB(int caller_pid, int fd, int real_fd, uint16 protocol,
uint16 lport, uint16 rport, uint32 rhost, int established)
{
CIB_chain *C;
int retval = 1;
C = (CIB_chain *)do_KRmalloc(caller_pid, sizeof(CIB_chain));
if (!C)
retval = 0;
else {
C->the_fd = fd;
C->real_fd = real_fd;
#ifndef BLOCK_OPEN
C->established = established;
#endif
C->the_CIB.protocol = protocol;
C->the_CIB.lport = lport;
C->the_CIB.rport = rport;
C->the_CIB.rhost = rhost;
Psem_obtain(CIB_SEM); /* Enter MUTEX */
C->next = CIB_list;
CIB_list = C;
Psem_release(CIB_SEM); /* Leave MUTEX */
}
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "make_CIB(%d{%d}, %d, %d, %d, %A, %d) returns %d",
fd, real_fd, protocol, lport, rport, rhost, established, retval);
#if 0
debug("si", "make_CIB() returns ", retval);
#endif
#endif
return retval;
}
static int change_CIB(int caller_pid, int fd, int real_fd, uint16 protocol,
uint16 lport, uint16 rport, uint32 rhost,
int established)
{
CIB_chain *C;
int retval = 1;
Psem_obtain(CIB_SEM); /* Enter MUTEX */
for (C = CIB_list; C; C = C->next) {
if (C->the_fd == fd) {
break;
}
}
if (!C)
retval = 0;
else {
C->real_fd = real_fd;
#ifndef BLOCK_OPEN
C->established = established;
#endif
C->the_CIB.protocol = protocol;
C->the_CIB.lport = lport;
C->the_CIB.rport = rport;
C->the_CIB.rhost = rhost;
Psem_release(CIB_SEM); /* Leave MUTEX */
}
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE,
"change_CIB(%d{%d}, %d, %d, %d, %A, %d) returns %d",
fd, real_fd, protocol, lport, rport, rhost, established, retval);
#if 0
debug("si", "make_CIB() returns ", retval);
#endif
#endif
return retval;
}
static CIB *find_CIB(int caller_pid, int fd)
{
register CIB_chain *C;
CIB *CC = 0;
Psem_obtain(CIB_SEM);
for (C = CIB_list; C; C = C->next) {
if (C->the_fd == fd) {
CC = &(C->the_CIB);
break;
}
}
Psem_release(CIB_SEM);
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "find_CIB(%d) returns %p", fd, (void *)CC);
#if 0
debug("sp", "find_CIB() returns ", (void *)CC);
#endif
#endif
return CC;
}
static void remove_CIB(int caller_pid, int fd)
{
register CIB_chain *C, *C2;
Psem_obtain(CIB_SEM);
C = CIB_list;
if (C && C->the_fd == fd) {
CIB_list = C->next;
do_KRfree(caller_pid, C);
} else {
for (C = CIB_list; C && C->next; C = C->next) {
if (C->next->the_fd == fd) {
C2 = C->next;
C->next = C2->next;
do_KRfree(caller_pid, C2);
break;
}
}
}
Psem_release(CIB_SEM);
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "remove_CIB() done");
#if 0
debug("s", "remove_CIB() done");
#endif
#endif
}
#ifndef BLOCK_OPEN
/* Blech blech blech. I really don't like having to keep state info
like this, but there appears to be no reliable way to get this info
on the fly. */
static int is_established(int caller_pid, int fd)
{
register CIB_chain *C;
Psem_obtain(CIB_SEM);
for (C = CIB_list; C; C = C->next) {
if (C->the_fd == fd) {
break;
}
}
Psem_release(CIB_SEM);
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "is_established(%d): %s", fd,
(!C ? "no such connection?" :
C->established ? "established" : "not established"));
#if 0
debug("sdsss", "Connection ", fd, " is ",
(C->established ? "" : "not "), "established");
#endif
#endif
return (C ? C->established : 0);
}
static void set_establish(int fd)
{
register CIB_chain *C;
Psem_obtain(CIB_SEM);
for (C = CIB_list; C; C = C->next) {
if (C->the_fd == fd) {
C->established = 1;
break;
}
}
Psem_release(CIB_SEM);
}
#endif /* BLOCK_OPEN */
static int16 get_real_fd(int caller_pid, int16 fd)
{
register CIB_chain *C;
int real_fd;
Psem_obtain(CIB_SEM);
for (C = CIB_list; C; C = C->next) {
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE,
"In get_real_fd(%d): checking CIB for %d{%d}",
fd, C->the_fd, C->real_fd);
#endif
if (C->the_fd == fd) {
real_fd = C->real_fd;
break;
}
}
Psem_release(CIB_SEM);
if (!C) {
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In get_real_fd(%d): no matching CIB found", fd);
#endif
return -1;
}
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE,
"get_real_fd(%d) returns underling fd %d", fd, real_fd);
#endif
return real_fd;
}
/* Incompatibility: obsize is ignored. MiNTnet does its own buffer
handling. */
int16 do_TCP_open(int caller_pid, uint32 hostaddr, int16 port, int16 tos,
uint16 obsize)
{
struct sockaddr_in addr;
int fd;
size_t scratch = sizeof(struct sockaddr_in);
int retval;
#ifdef MULTITHREAD
Psem_obtain(FD_SEM);
#endif
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering TCP_open(%A, %d, %d, %d)",
hostaddr, port, tos, obsize);
#if 0
debug("slslslslsisisis", "Entering TCP_open(", (hostaddr>>24), ".",
(hostaddr>>16)&0xFFL, ".", (hostaddr>>8)&0xFFL, ".", hostaddr&0xFFL,
", ", port, ", ", tos, ", ", obsize, ")");
#endif
#endif
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = hostaddr;
addr.sin_port = port;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): socket() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
#if 0
debug("sdss", "socket() returns ", errno, ": ", sys_errlist[errno]);
#endif
errno = save_errno;
#endif
switch (errno) {
case EMFILE:
retval = E_NOCCB; break;
case ENOMEM:
retval = E_NOMEM; break;
case ENOENT:
case EAFNOSUPPORT:
case ESOCKTNOSUPPORT:
case EPROTONOSUPPORT:
case EPROTOTYPE:
retval = -1000 - errno; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, retval);
#endif
return retval;
}
if (hostaddr != 0) {
#ifndef BLOCK_OPEN
/* From the STiK specs: "TCP_open() returns immediately, without
waiting for the connection to establish." */
if (fcntl(fd, F_SETFL, O_NDELAY) < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): fcntl() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
errno = save_errno;
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, E_CONNECTFAIL);
#endif
return E_CONNECTFAIL;
}
#endif
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): connect() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
#if 0
debug("sdss", "connect() returns ", errno, ": ", sys_errlist[errno]);
#endif
errno = save_errno;
#endif
switch (errno) {
case EBADF:
case EISCONN:
retval = E_BADHANDLE; break;
case EINVAL:
retval = E_LISTEN; break;
case ETIMEDOUT:
retval = E_CNTIMEOUT; break;
case ECONNREFUSED:
retval = E_REFUSE; break;
case EALREADY:
case EINPROGRESS:
case EINTR:
case EAFNOSUPPORT:
case EDESTADDRREQ:
case EOPNOTSUPP:
retval = -1000 - errno; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, retval);
#endif
return retval;
}
/* Cobble up the CIB structure for this connection. The only thing
we don't yet have is the local port number. */
if (getsockname(fd, (struct sockaddr *)&addr, &scratch) < 0) {
int the_err = errno;
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): getsockname() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
#if 0
debug("sdss", "getsockname() returns ", errno, ": ", sys_errlist[errno]);
#endif
#endif
close(fd);
switch (the_err) {
case EBADF:
retval = E_BADHANDLE; break;
case EINVAL:
case EOPNOTSUPP:
default:
retval = -1000 - the_err; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, retval);
#endif
return retval;
}
if (!make_CIB(caller_pid, fd, fd, P_TCP, addr.sin_port, port, hostaddr, 0))
{
close(fd);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): out of memory for make_CIB()",
hostaddr, port, tos, obsize);
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, E_NOMEM);
#endif
return E_NOMEM;
}
#ifdef MULTITHREAD
Psem_release(FD_SEM);
#endif
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL,
"TCP_open(%A, %d, %d, %d) returns socket handle %d{%d}",
hostaddr, port, tos, obsize, fd, fd);
#if 0
debug("si", "TCP_open() returns socket handle ", fd);
#endif
#endif
return fd;
} else {
int in_fd;
size_t addr_size = sizeof(addr);
/* From the STiK specs: "If rhost is 0, then the connection becomes
a LISTEN socket, and waits for a connection request from a remote
host." */
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): bind() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
errno = save_errno;
#endif
switch (errno) {
case EBADF:
case EADDRINUSE:
case EINVAL:
retval = E_BADHANDLE; break;
case EAFNOSUPPORT:
case EACCES:
case EDESTADDRREQ:
retval = -1000 - errno; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, retval);
#endif
close(fd);
return retval;
}
if (listen(fd, 5) < 0) {
int the_err = errno;
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): listen() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
#endif
close(fd);
switch (the_err) {
case EBADF:
retval = E_BADHANDLE; break;
case EDESTADDRREQ:
case EOPNOTSUPP:
case EINVAL:
retval = -1000 - the_err; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, retval);
#endif
return retval;
}
/* Cobble up the CIB structure for this connection. The only thing
we don't yet have is the local port number. */
if (getsockname(fd, (struct sockaddr *)&addr, &scratch) < 0) {
int the_err = errno;
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): getsockname() returns %d (\"%s\")",
hostaddr, port, tos, obsize, errno, sys_errlist[errno]);
#endif
close(fd);
switch (the_err) {
case EBADF:
retval = E_BADHANDLE; break;
case EINVAL:
case EOPNOTSUPP:
default:
retval = -1000 - the_err; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, retval);
#endif
return retval;
}
in_fd = NEXT_LISTEN_COOKIE();
if (!make_CIB(caller_pid, in_fd, fd, P_TCP, addr.sin_port, port,
addr.sin_addr.s_addr, 1))
{
close(fd);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_open(%A, %d, %d, %d): out of memory for make_CIB()",
hostaddr, port, tos, obsize);
LOG(caller_pid, DBG_ERROR,
"TCP_open(%A, %d, %d, %d) returns error code %d",
hostaddr, port, tos, obsize, E_NOMEM);
#endif
return E_NOMEM;
}
#ifdef MULTITHREAD
Psem_release(FD_SEM);
#endif
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL,
"TCP_open(%A, %d, %d, %d) returns listen socket handle %d{%d}",
hostaddr, port, tos, obsize, in_fd, fd);
#endif
return in_fd;
}
}
/* Incompatibility: timeout is ignored */
int16 do_TCP_close(int caller_pid, int16 fd, int16 timeout)
{
int ret;
int real_fd = REAL_FD(caller_pid, fd);
#ifdef MULTITHREAD
Psem_obtain(FD_SEM);
#endif
remove_CIB(caller_pid, real_fd);
ret = close(real_fd);
#ifdef DEBUG
LOG(caller_pid, (ret < 0 ? DBG_ERROR : DBG_SYSCALL),
"TCP_close(%d{%d}, %d) returns %s%d", fd, real_fd, timeout,
(ret < 0 ? "error code " : ""), (ret < 0 ? E_BADCLOSE : 0));
#endif
#ifdef MULTITHREAD
Psem_release(FD_SEM);
#endif
if (ret < 0)
return E_BADCLOSE;
else return 0;
}
/* Incompatibility: TCP_send() will probably be blocking; after all, I
can't return E_OBUFFULL in good faith if I don't honor obsize from
TCP_open(). */
/* Incompatibility: What is TCP_send() supposed to do if the non-blocking
TCP_open() hasn't finished? The STiK specs don't give a clue; I'm
gonna just turn off O_NDELAY and wait for the connection to establish. */
int16 do_TCP_send(int caller_pid, int16 fd, char* buf, int16 buflen)
{
int n, tot = 0;
int real_fd = REAL_FD(caller_pid, fd);
int retval;
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering TCP_send(%d{%d}, \"%S\")",
fd, real_fd, buflen, buf);
#if 0
debug("sSs", "in TCP_send(\"", buflen, buf, "\")");
#endif
#endif
#ifndef BLOCK_OPEN
if (!is_established(caller_pid, fd))
do_TCP_wait_state(fd, (IS_LISTEN_COOKIE(fd) ? TLISTEN : TESTABLISH), 0);
#endif
for (;;) {
n = write(real_fd, buf + tot, buflen - tot);
if (n < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In TCP_send(%d{%d}, \"%S\"): write() returns %d (\"%s\")",
fd, real_fd, buflen, buf, errno, sys_errlist[errno]);
errno = save_errno;
#endif
switch (errno) {
case EBADF:
retval = E_BADHANDLE; break;
case ENOSPC:
retval = E_NOMEM; break;
case ENXIO:
retval = E_LOSTCARRIER; break;
/* case EAGAIN: */
case ERANGE:
retval = E_OBUFFULL; break;
default:
retval = -1000 - errno; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_send(%d{%d}, \"%S\") returns error code %d",
fd, real_fd, buflen, buf, retval);
#endif
return retval;
} else {
tot += n;
if (tot >= buflen) {
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL,
"TCP_send(%d{%d}, \"%S\") returns E_NORMAL",
fd, real_fd, buflen, buf);
#endif
return E_NORMAL;
}
}
}
}
/* Incompatibility: Yeah, right, like I could reproduce the behavior of
this function... This function seems to have two main uses: with
TESTABLISH, wait for a non-listen port to finish opening; and with
TLISTEN, accept() on a listen port. */
int16 do_TCP_wait_state(int caller_pid, int16 fd, int16 state, int16 timeout)
{
auto fd_set read_set, write_set, except_set;
struct timeval T;
int n;
int real_fd = REAL_FD(caller_pid, fd);
int retval;
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering TCP_wait_state(%d{%d}, %d, %d)",
fd, real_fd, state, timeout);
#if 0
debug("s", "in TCP_wait_state()");
#endif
#endif
if (IS_LISTEN_COOKIE(fd)) {
int in_fd;
struct sockaddr_in addr, addr2;
size_t addr_size = sizeof(struct sockaddr_in);
#ifdef MULTITHREAD
Psem_obtain(FD_SEM);
#endif
if ((in_fd = accept(real_fd, (struct sockaddr *)&addr, &addr_size)) < 0) {
int the_err = errno;
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_wait_state(%d{%d}, %d, %d): accept() returns %d (\"%s\")",
fd, real_fd, state, timeout, errno, sys_errlist[errno]);
#if 0
debug("sdss", "accept() returns ", errno, ": ", sys_errlist[errno]);
#endif
#endif
switch (the_err) {
case EBADF:
retval = E_BADHANDLE; break;
case EMFILE:
retval = E_NOCCB; break;
case ENOMEM:
retval = E_NOMEM; break;
case EOPNOTSUPP:
case EWOULDBLOCK:
case ECONNABORTED:
case EINVAL:
case EINTR:
retval = -1000 - the_err; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_wait_state(%d{%d}, %d, %d) returns error code %d",
fd, real_fd, state, timeout, retval);
#endif
return retval;
}
/* Have to change the CIB to reflect the new state of the socket.
Part of the data we need came back from accept(); we need to get
the rest via getsockname(). */
addr_size = sizeof(struct sockaddr_in);
if (getsockname(in_fd, (struct sockaddr *)&addr2, &addr_size) < 0) {
int the_err = errno;
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_wait_state(%d{%d}, %d, %d): "
"getsockname() returns %d (\"%s\")",
fd, real_fd, state, timeout, errno, sys_errlist[errno]);
#if 0
debug("sdss", "getsockname() returns ", errno, ": ", sys_errlist[errno]);
#endif
#endif
close(in_fd);
switch (the_err) {
case EBADF:
retval = E_BADHANDLE; break;
case EINVAL:
case EOPNOTSUPP:
default:
retval = -1000 - the_err; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_wait_state(%d{%d}, %d, %d) returns error code %d",
fd, real_fd, state, timeout, retval);
#endif
return retval;
}
if (!change_CIB(caller_pid, fd, in_fd, P_TCP, addr2.sin_port,
addr.sin_port, addr.sin_addr.s_addr, 1))
{
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In TCP_wait_state(%d{%d}, %d, %d): unable to update CIB "
"for listen port",
fd, real_fd, state, timeout);
LOG(caller_pid, DBG_ERROR,
"TCP_wait_state(%d{%d}, %d, %d) returns error code %d",
fd, real_fd, state, timeout, E_NOMEM);
#endif
return E_NOMEM;
}
/* In STiK, an accept() apparently "eats" the listen()'ed socket; we
emulate that by closing it. */
close(real_fd);
#ifdef MULTITHREAD
Psem_release(FD_SEM);
#endif
return E_NORMAL;
} else {
#ifndef BLOCK_OPEN
if (is_established(caller_pid, fd))
return E_NORMAL;
n = fcntl(real_fd, F_GETFL);
fcntl(real_fd, F_SETFL, n & ~O_NDELAY);
FD_ZERO(&read_set);
FD_SET(real_fd, &read_set);
FD_ZERO(&write_set);
FD_SET(real_fd, &write_set);
FD_ZERO(&except_set);
FD_SET(real_fd, &except_set);
T.tv_sec = timeout;
T.tv_usec = 0;
n = select(real_fd + 1, &read_set, &write_set, &except_set, &T);
if (n == 0) {
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_wait_state(%d{%d}, %d, %d) returns E_USERTIMEOUT",
fd, real_fd, state, timeout);
#endif
return E_USERTIMEOUT;
} if (n > 0) {
set_establish(fd);
return E_NORMAL;
}
#ifdef DEBUG
{
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In TCP_wait_state(%d{%d}, %d, %d): select() returned %d (\"%s\")",
fd, real_fd, state, timeout, errno, sys_errlist[errno]);
errno = save_errno;
}
#endif
switch (errno) {
case EBADF:
retval = E_BADHANDLE; break;
case EINTR:
case EINVAL:
default:
retval = -1000 - errno; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"TCP_wait_state(%d{%d}, %d, %d) returns error code %d",
fd, real_fd, state, timeout, retval);
#endif
return retval;
#else
return E_NORMAL;
#endif /* BLOCK_OPEN */
}
}
int16 do_UDP_open(int caller_pid, uint32 hostaddr, int16 port)
{
struct sockaddr_in addr;
int fd;
size_t scratch = sizeof(struct sockaddr_in);
int retval;
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering UDP_open(%A, %d)",
hostaddr, port);
#if 0
debug("s", "in UDP_open()");
#endif
#endif
#ifdef MULTITHREAD
Psem_obtain(FD_SEM);
#endif
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = hostaddr;
addr.sin_port = port;
memset(addr.sin_zero, 0, 8);
fd = socket(AF_UNIX, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In UDP_open(%A, %d): socket() returned %d (\"%s\")",
hostaddr, port, errno, sys_errlist[errno]);
errno = save_errno;
#endif
switch (errno) {
case EMFILE:
retval = E_NOCCB; break;
case ENOMEM:
retval = E_NOMEM; break;
case ENOENT:
case EAFNOSUPPORT:
case ESOCKTNOSUPPORT:
case EPROTONOSUPPORT:
case EPROTOTYPE:
retval = -1000 - errno; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"UDP_open(%A, %d) returns error code %d",
hostaddr, port, retval);
#endif
return retval;
}
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
int the_err = errno;
close(fd);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In UDP_open(%A, %d): socket() returned %d (\"%s\")",
hostaddr, port, the_err, sys_errlist[the_err]);
#endif
switch (the_err) {
case EBADF:
case EISCONN:
retval = E_BADHANDLE; break;
case EINVAL:
retval = E_LISTEN; break;
case ETIMEDOUT:
retval = E_CNTIMEOUT; break;
case ECONNREFUSED:
retval = E_REFUSE; break;
case EALREADY:
case EINPROGRESS:
case EINTR:
retval = fd; break;
case EAFNOSUPPORT:
case EDESTADDRREQ:
case EOPNOTSUPP:
retval = -1000 - the_err; break;
default:
retval = E_CONNECTFAIL; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"UDP_open(%A, %d) returns error code %d",
hostaddr, port, retval);
#endif
return retval;
}
/* Cobble up the CIB structure for this connection. The only thing
we don't yet have is the local port number. */
if (getsockname(fd, (struct sockaddr *)&addr, &scratch) < 0) {
int the_err = errno;
close(fd);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In UDP_open(%A, %d): getsockname() returned %d (\"%s\")",
hostaddr, port, the_err, sys_errlist[the_err]);
#endif
switch (the_err) {
case EBADF:
retval = E_BADHANDLE; break;
case EINVAL:
case EOPNOTSUPP:
default:
retval = -1000 - the_err; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"UDP_open(%A, %d) returns error code %d",
hostaddr, port, retval);
#endif
return retval;
}
if (!make_CIB(caller_pid, fd, fd, P_UDP, addr.sin_port, port, hostaddr, 1)) {
close(fd);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In UDP_open(%A, %d): out of memory for make_CIB()", hostaddr, port);
LOG(caller_pid, DBG_ERROR,
"UDP_open(%A, %d) returns error code %d",
hostaddr, port, E_NOMEM);
#endif
return E_NOMEM;
}
#ifdef MULTITHREAD
Psem_release(FD_SEM);
#endif
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL,
"UDP_open(%A, %d) returns port %d", hostaddr, port, fd);
#endif
return fd;
}
int16 do_UDP_close(int caller_pid, int16 fd)
{
int ret;
int real_fd = REAL_FD(caller_pid, fd);
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering UDP_close(%d{%d})", fd, real_fd);
#if 0
debug("s", "in UDP_close()");
#endif
#endif
#ifdef MULTITHREAD
Psem_obtain(FD_SEM);
#endif
remove_CIB(caller_pid, fd);
ret = close(real_fd);
#ifdef MULTITHREAD
Psem_release(FD_SEM);
#endif
if (ret < 0) {
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"UDP_open(%d{%d}) returns error code %d",
fd, real_fd, E_BADCLOSE);
#endif
return E_BADCLOSE;
} else return 0;
}
int16 do_UDP_send(int caller_pid, int16 fd, char* buf, int16 buflen)
{
int n, tot = 0;
int real_fd = REAL_FD(caller_pid, fd);
int retval;
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering UDP_send(%d{%d}, \"%S\")",
fd, real_fd, buflen, buf);
#if 0
debug("s", "in UDP_send()");
#endif
#endif
n = write(real_fd, buf + tot, buflen - tot);
if (n < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In UDP_send(%d{%d}, \"%S\"): write() returned %d (\"%s\")",
fd, real_fd, buflen, buf, errno, sys_errlist[errno]);
errno = save_errno;
#endif
switch (errno) {
case EBADF:
retval = E_BADHANDLE; break;
case ENOSPC:
retval = E_NOMEM; break;
case ENXIO:
retval = E_LOSTCARRIER; break;
case ERANGE:
retval = E_OBUFFULL; break;
default:
retval = -1000 - errno; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"UDP_send(%d{%d}, \"%S\") returns error code %d",
fd, real_fd, buflen, buf, retval);
#endif
return retval;
} else {
return E_NORMAL;
}
}
/* This is just an estimate, but it should work. */
int16 do_CNbyte_count(int caller_pid, int16 fd)
{
long l;
int real_fd = REAL_FD(caller_pid, fd);
if (fcntl(real_fd, FIONREAD, &l) < 0) {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In CNbyte_count(%d{%d}): fcntl() returns %d (\"%s\")",
fd, real_fd, errno, sys_errlist[errno]);
errno = save_errno;
LOG(caller_pid, DBG_ERROR,
"CNbyte_count(%d{%d}) returns error code %d",
fd, real_fd, -1000 - errno);
#endif
return -1000 - errno;
}
if (l == NO_LIMIT) {
#ifndef BLOCK_OPEN
/* Tricky. This could be either EOF or the connection not
established. In the latter case, we have the "0 or E_NODATA?"
puzzler from below. */
if (!is_established(caller_pid, fd)) {
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "CNbyte_count(%d{%d}) returns 0",
fd, real_fd);
#endif
return 0;
}
#endif
return E_EOF;
}
/* Now here's a puzzler. If there's no data available should I return
0 or E_NODATA? Dan Ackerman's mailer assumes the former, so that's
what I'll do. */
if (l == 0) {
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "CNbyte_count(%d{%d}) returns 0",
fd, real_fd);
#endif
/* Here's a sneaky trick: If there's no data, surrender the rest of
our timeslice so that the STiK app doesn't eat all CPU time
spinning its wheels. */
Syield();
return 0;
}
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "CNbyte_count(%d{%d}) returns %l",
fd, real_fd, l);
#endif
#ifndef BLOCK_OPEN
/* I really don't like doing this here, since this function will
likely be called in a tight loop, but it has to be done, and this
is about the only place I can do it. */
set_establish(fd);
#endif
return l;
}
/* There should probably be a more efficient way of doing this than
calling read() */
int16 do_CNget_char(int caller_pid, int16 fd)
{
char c;
int n;
int real_fd = REAL_FD(caller_pid, fd);
int retval;
n = read(real_fd, &c, 1);
if (n > 0) {
#ifndef BLOCK_OPEN
/* Again, I don't really like doing this here, but I don't think I
can avoid it. */
set_establish(fd);
#endif
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "CNget_char(%d{%d}) returns '%c' (%x)",
fd, real_fd, c, (unsigned)c);
#endif
return c;
}
if (n == 0) {
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "CNget_char(%d{%d}) returns EOF",
fd, real_fd);
#endif
return E_EOF;
}
#ifdef DEBUG
{
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In CNget_char(%d{%d}): read() returns %d (\"%s\")",
fd, real_fd, errno, sys_errlist[errno]);
errno = save_errno;
}
#endif
switch (errno) {
case EWOULDBLOCK:
retval = E_NODATA; break;
case EBADF:
retval = E_BADHANDLE; break;
default:
retval = -1000 - errno; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"CNget_char(%d{%d}) returns error code %d",
fd, real_fd, retval);
#endif
return retval;
}
/* We fake this by reading whatever is there and building a a fake NDB
for it */
NDB* do_CNget_NDB(int caller_pid, int16 fd)
{
int bufsize = do_CNbyte_count(caller_pid, fd);
char *buf;
NDB *ndb = 0;
int real_fd = REAL_FD(caller_pid, fd);
if (bufsize <= 0) {
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In CNget_NDB(%d{%d}): no data available; returning 0",
fd, real_fd);
#endif
return 0;
}
buf = do_KRmalloc(caller_pid, bufsize);
if (!buf) {
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In CNget_NDB(%d{%d}): out of memory for NDB data; returning 0",
fd, real_fd);
#endif
return 0;
}
ndb = (NDB *)do_KRmalloc(caller_pid, sizeof(NDB));
if (!ndb) {
do_KRfree(caller_pid, buf);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In CNget_NDB(%d{%d}): out of memory for NDB; returning 0",
fd, real_fd);
#endif
return 0;
}
bufsize = read(real_fd, buf, bufsize);
if (bufsize <= 0) {
do_KRfree(caller_pid, buf);
do_KRfree(caller_pid, (char *)ndb);
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In CNget_NDB(%d{%d}): no data read; returning 0",
fd, real_fd);
#endif
return 0;
}
ndb->ptr = ndb->ndata = buf;
ndb->len = bufsize;
ndb->next = 0;
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "CNget_NDB(%d{%d}) reads data |%s|",
fd, real_fd, ndb->ndata);
LOG(caller_pid, DBG_SYSCALL, "CNget_NDB(%d{%d}) reads %d bytes, returns %p",
fd, real_fd, ndb->len, (void *)ndb);
#endif
return ndb;
}
/* Incompatibility: CNget_block() is blocking, because there's no way
to say to MiNTnet, "If you can't fill the entire buffer in one try,
don't read anything" */
int16 do_CNget_block(int caller_pid, int16 fd, char* buf, int16 len)
{
int n, tot = 0;
int real_fd = REAL_FD(caller_pid, fd);
int retval;
#ifndef BLOCK_OPEN
if (!is_established(caller_pid, fd))
do_TCP_wait_state(fd, 0, 0);
#endif
for (;;) {
n = read(real_fd, buf + tot, len - tot);
if (n > 0) {
tot += n;
if (tot >= len) {
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE,
"CNget_block(%d{%d}, %d) reads |%s|",
fd, real_fd, len, buf);
LOG(caller_pid, DBG_SYSCALL,
"CNget_block(%d{%d}, %d) returns length %d",
fd, real_fd, len, tot);
#endif
return tot;
}
} else if (n == 0) {
/* Incompatibility: If we read part of the buffer before getting
the EOF, there's no way to "unread" the read data. I'm torn
between returning the number of bytes actually read and
trusting the caller to notice, or returning E_EOF and
discarding the partial buffer. For now, I'll do the former. */
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE,
"CNget_block(%d{%d}, %d) reads partial buffer |%s|",
fd, real_fd, len, buf);
LOG(caller_pid, DBG_SYSCALL,
"CNget_block(%d{%d}, %d) returns partial buffer length %d",
fd, real_fd, len, tot);
#endif
return tot;
} else {
#ifdef DEBUG
int save_errno = errno;
LOG(caller_pid, DBG_ERROR,
"In CNget_block(%d{%d}, %d): read() returns %d (\"%s\")",
fd, real_fd, len, errno, sys_errlist[errno]);
errno = save_errno;
#endif
switch (errno) {
case EWOULDBLOCK:
retval = E_NODATA; break;
case EBADF:
retval = E_BADHANDLE; break;
default:
retval = -1000 - errno; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"CNget_block(%d{%d}, %d) returns error code %d",
fd, real_fd, len, retval);
#endif
return retval;
}
}
}
int16 do_resolve(int caller_pid, char *hostname, char **realname,
uint32 *addrs, int16 naddrs)
{
register struct hostent *H = 0;
register char **raddr;
register int i;
int retval;
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering resolve(\"%s\", %p, %p, %d)",
hostname, (void *)realname, (void *)addrs, naddrs);
#if 0
debug("ssspspsis", "in resolve(\"", hostname, "\", ", (void *)realname,
", ", (void *)addrs, ", ", naddrs, ")");
#endif
#endif
H = gethostbyname(hostname);
if (!H) {
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"In resolve(\"%s\", %p, %p, %d): gethostbyname() returns %d (\"%s\")",
hostname, (void *)realname, (void *)addrs, naddrs,
h_errno, sys_errlist[h_errno]);
#if 0
debug("s", "gethostbyname() returns NULL");
#endif
#endif
switch (h_errno) {
case HOST_NOT_FOUND:
retval = E_NOHOSTNAME; break;
case NO_DATA:
retval = E_DNSNOADDR; break;
case TRY_AGAIN:
case NO_RECOVERY:
default:
retval = E_CANTRESOLVE; break;
}
#ifdef DEBUG
LOG(caller_pid, DBG_ERROR,
"resolve(\"%s\", %p, %p, %d) returns error code %d",
hostname, (void *)realname, (void *)addrs, naddrs, retval);
#endif
return retval;
}
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE,
"In resolve(\"%s\", %p, %p, %d): gethostbyname() returns %p",
hostname, (void *)realname, (void *)addrs, naddrs, (void *)H);
#endif
if (realname) {
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "Copying name \"%s\"", H->h_name);
#endif
*realname = do_KRmalloc(caller_pid, strlen(H->h_name) + 1);
strcpy(*realname, H->h_name);
}
/* BUG: assumes addresses returned have type struct in_addr */
for (i = 0, raddr = H->h_addr_list; *raddr && i < naddrs; i++, raddr++) {
#ifdef DEBUG
LOG(caller_pid, DBG_TRACE, "Copying address %A to array element %d",
((struct in_addr *)(*raddr))->s_addr, i);
#endif
addrs[i] = ((struct in_addr *)(*raddr))->s_addr;
}
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL,
"resolve(\"%s\", %p, %p, %d) returns %d",
hostname, (void *)realname, (void *)addrs, naddrs, i);
#endif
return i;
}
/* (possible) Incompatibility: It is possible, at least in theory, for
find_CIB() to return NULL; the STiK specs seem to imply that this
cannot happen, so the receiving app probably doesn't check for it.
In practice, find_CIB() should not return NULL, except for file
descriptors that aren't sockets opened by GlueSTiK. */
CIB* do_CNgetinfo(int caller_pid, int16 fd)
{
register CIB *C;
#ifdef DEBUG
LOG(caller_pid, DBG_SYSCALL, "Entering CNgetinfo(%d)", fd);
#if 0
debug("s", "in do_CNgetinfo()");
#endif
#endif
C = find_CIB(caller_pid, fd);
#ifdef DEBUG
LOG(caller_pid, (!C ? DBG_ERROR : DBG_SYSCALL),
"CNgetinfo(%d) returns %p", fd, (void *)C);
#endif
return C;
}
void cleanup_net()
{
Psem_obtain(CIB_SEM);
Psem_destroy(CIB_SEM);
#ifdef MULTITHREAD
Psem_obtain(FD_SEM);
Psem_destroy(FD_SEM);
#endif
}